package gov.nasa.anml.lifted;

import java.util.ArrayList;

import gov.nasa.anml.PDDL;
import gov.nasa.anml.State;
import gov.nasa.anml.PDDL.Time;
import gov.nasa.anml.utility.SimpleObject;
import gov.nasa.anml.utility.SimpleString;
import static gov.nasa.anml.lifted.IdentifierCode.*;

//don't need reference objects in background representation
//because fluent as an expression is clearly a reference to its value,
//and is implemented by a java reference anyways, so there is only
//space overhead to a FluentReference object.

//inside other objects, like Effects,
//public int fID;
//saves space and time over public Fluent f;
//but has no behavior -- one has to remember to do
//s.get(fID), instead of f.value(s).
//if Fluent implements a pool, then one could make static methods
//for the best of both worlds:
//Fluent.value(fID,s) would be roughly equivalent to
//Fluent.get(fID).value(s) or
//f.value(s) when one already has the reference.
//except that the fluent object itself is not needed at a sufficiently
//ground level, so that Fluent.get(fID) can be skipped.  
//if both ways are made final, and thus probably inlined, then 
//the static method saves on an object field reference:
//s.get(fID) instead of s.get(f.id);

public class Fluent<T extends SimpleObject<? super T>> extends IdentifierImp<T,T> implements Expression<T,T> {
	public Type<T> type;
	public Expression<T,T> init;

	public Fluent() {
	}
	public Fluent(String n) {
		name = new SimpleString(n);
	}
	public Fluent(SimpleString n) {
		super(n);
	}
	public Fluent(SimpleString n,Type<T> t) {
		super(n);
		type = t;
	}

	public Fluent(SimpleString n, Type<T> t, Expression<T,T> init) {
		super(n);
		this.type = t;
		if (init != null)
			this.init = new Assign<T>(this,init);
	}

	public TypeCode typeCode() {
		return type.typeCode();
	}

	public T value(State s) {
		return (T) s.resolveFluent(id).value;
	}

	public History<T> storage(State p, State c) {
		return (History<T>) c.resolveFluent(id);
	}

	public T init(State s) {
		History<T> place = new History<T>(init.value(s));
		s.fluents.put(id,place);
		return place.value;
	}

	public IdentifierCode idCode() {
		return Fluent;
	}

	public boolean apply(State p, int contextID, State c) {
		if (type.typeCode() != TypeCode.Boolean)
			return false;
		if (value(p) != ANMLBoolean.True)
			return false;
		return true;
	}

	public transient PDDL.Predicate boolPDDL;
	public transient PDDL.Function floatPDDL;
	public transient PDDL.PredicateReference asPDDLPredicateReference;
	public transient PDDL.FunctionReference asPDDLFunctionReference;

	public PDDL.Expression translateLValue(PDDL pddl,Interval unit) {
		switch(typeCode()) {
		case Boolean:
			return asPDDLPredicateReference;
		case Float:
			return asPDDLFunctionReference;
		default:
			System.err.println("Oops!");
		}
		return super.translateLValue(pddl,unit);
	}

	public void translateDecl(PDDL pddl,Interval unit) {
		if (typeCode() == TypeCode.Boolean) {
			if (boolPDDL != null)
				return;
			int length = pddl.bufAppend(name);
			boolPDDL = pddl.new Predicate(pddl.bufToString());
			asPDDLPredicateReference = pddl.new PredicateReference(boolPDDL);
			pddl.bufReset(length);
			pddl.predicates.add(boolPDDL);
			boolPDDL.context.addAll(pddl.context);
			if (init != null)
				init.translateStmt(pddl,unit,PDDL.Time.Start);
		} else if (typeCode() == TypeCode.Float) {
			if (floatPDDL != null)
				return;
			int length = pddl.bufAppend(name);
			floatPDDL = pddl.new Function(pddl.bufToString());
			asPDDLFunctionReference = pddl.new FunctionReference(floatPDDL);
			pddl.bufReset(length);
			pddl.functions.add(floatPDDL);
			floatPDDL.context.addAll(pddl.context);
			if (init != null)
				init.translateStmt(pddl,unit,PDDL.Time.Start);
		} else if (typeCode() == TypeCode.Integer) {
			// be nice by relaxing integers to floats in the compilation to PDDL
			if (floatPDDL != null)
				return;
			int length = pddl.bufAppend(name);
			floatPDDL = pddl.new Function(pddl.bufToString());
			asPDDLFunctionReference = pddl.new FunctionReference(floatPDDL);
			pddl.bufReset(length);
			pddl.functions.add(floatPDDL);
			floatPDDL.context.addAll(pddl.context);
			type = (Type<T>) Unit.floatType; 
			// make sure all future references see this as a float
			if (init != null)
				init.translateStmt(pddl,unit,PDDL.Time.Start);
		}
	}

	public PDDL.Expression translateExpr(PDDL pddl, Interval unit) {
		switch(typeCode()) {
		case Boolean:
			return asPDDLPredicateReference;
		case Float:
			return asPDDLFunctionReference;
		default:
			System.err.println("Oops!");
		}
		return super.translateExpr(pddl,unit);
	}


}
